import java.lang.Character;
import java.util.Scanner;

/**
 * @modulename ParseLine
 * @description This module separates a line of source code into 4 parts (label,
 *              directive/instruction, operand, and comment).
 * @callsequence ParseLine variableName = new ParseLine("source code line");,
 *               where variableName is defined by the user and then used to
 *               reference the line of parsed code.
 * @inparam A sourceCode String
 * @outparam An array of strings where position 0 is the label, position 1 is
 *           the directive or instruction, position 2 is the operand field, and
 *           position 3 is the comment. If one of these fields does not exists
 *           then a null value will be in it's place.
 * @errorstested various lines of code were given and currently this module can
 *               not handle empty strings or only white space.
 * @errorsgenerated Initially, an empty string/only white space would generate
 *                  an out of bounds error but this has been fixed.
 * @author Nicholas Lister
 * @createdate 10/14/09
 * @modlog <p>
 *         <ul>
 *         Who/When/Why
 *         <li>Nick, 10/16/09, Added parseOperand module and added comments and
 *         cleaned up code to the code
 *         <li>Nick, 10/19/09, Added parseExpression module
 *         <li>Nick, 10/26/09, refined parse line code
 *         <li>Nick, 10/28/09, added parseExpressionKeepOperands module
 *         <li>Nick, 11/6/09, parseOperands,parseExpression,and
 *         parseExpressionKeepOperands adjusted to handle leading minus signs
 *         </ul>
 *         Coding Standards Met: "Signed Code Leader(Jaron)"
 *         <p>
 *         Testing Standards Met: "Signed Test Leader(Jamie)"
 */
public class ParseLine {
	// position 0 = label
	// position 1 = instruction or directive
	// position 2 = operand
	// position 3 = comment
	private String[] syntaxLine = new String[4];
	private Boolean isEmptyLine = Boolean.FALSE;

	public ParseLine(String sourceCodeLine) {
		// comment check
		if (sourceCodeLine.length() > 0) {
			if (sourceCodeLine.contains(":")) {
				int commentIndex = sourceCodeLine.indexOf(":");
				syntaxLine[3] = sourceCodeLine.substring(commentIndex).trim();
				sourceCodeLine = sourceCodeLine.substring(0, commentIndex);
			}
		}

		if (sourceCodeLine.length() > 0) {
			// get leading character of the line of code
			char leadingCharacter = sourceCodeLine.charAt(0);
			// put line of code in a scanner
			Scanner scan = new Scanner(sourceCodeLine);

			// check if the leading character is white space
			if (Character.isWhitespace(leadingCharacter)) {
				if (scan.hasNext()) {
					// no label present
					// get the next token
					String token = scan.next();
					// this token can only be the instruction/directive
					syntaxLine[1] = token.trim();
					// check if there is anything after the
					// instruction/directive
					if (scan.hasNext()) {
						// next token is the operand
						token = scan.nextLine();
						token = token.trim();
						syntaxLine[2] = token.trim();
					}
				} else {
					// Empty Line
					// Send empty line in Label position as warning.
					this.isEmptyLine = Boolean.TRUE;
				}
			}
			// if there is something in the first field then it must be a label
			else {
				// put the first token in the label field
				String token = scan.next();
				syntaxLine[0] = token.trim();

				// check if anything comes after the label
				if (scan.hasNext()) {
					// next token is either instruction or directive
					token = scan.next();

					syntaxLine[1] = token.trim();

					// check for operand
					if (scan.hasNext()) {
						// next token is the operand
						token = scan.nextLine();
						token = token.trim();
						syntaxLine[2] = token.trim();

					}
				}
			}
		} else {
			isEmptyLine = Boolean.TRUE;
		}
	}

	/**
	 * @modulename label
	 * @description returns the label field of the line of source code
	 * @callsequence parsedSourceCodeLine.label() where parsedSourceCodeLine is
	 *               the name the user gave the ParseLine variable when it was
	 *               declared.
	 * @inparam none
	 * @outparam returns a string representing the label field for a parsed line
	 *           of source code
	 * @errorstested tested by parsing a complete test program (SourceCode.txt)
	 * @errorsgenerated no known errors
	 * @author Nicholas Lister
	 * @createdate 10/14/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// returns the label field of the line of source code
	public String label() {
		return this.syntaxLine[0];
	}

	/**
	 * @modulename instruction
	 * @description returns the instruction field of the line of source code
	 * @callsequence parsedSourceCodeLine.instruction() where
	 *               parsedSourceCodeLine is the name the user gave the
	 *               ParseLine variable when it was declared.
	 * @inparam none
	 * @outparam returns a string representing the instruction field for a
	 *           parsed line of source code
	 * @errorstested tested by parsing a complete test program (SourceCode.txt)
	 * @errorsgenerated no known errors
	 * @author Nicholas Lister
	 * @createdate 10/14/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// returns the instruction/directive field of the line of source code
	public String instruction() {
		return this.syntaxLine[1];
	}

	/**
	 * @modulename operand
	 * @description returns the operand field of the line of source code
	 * @callsequence parsedSourceCodeLine.operand() where parsedSourceCodeLine
	 *               is the name the user gave the ParseLine variable when it
	 *               was declared.
	 * @inparam none
	 * @outparam returns a string representing the operand field for a parsed
	 *           line of source code
	 * @errorstested tested by parsing a complete test program (SourceCode.txt)
	 * @errorsgenerated Initially, there was errors with CHR operand types.
	 *                  However, these bugs have been worked out (to the best of
	 *                  my knowledge).
	 * @author Nicholas Lister
	 * @createdate 10/14/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// returns the operand field of the line of source code
	public String operand() {
		return this.syntaxLine[2];
	}

	/**
	 * @modulename label
	 * @description returns the label field of the line of source code
	 * @callsequence parsedSourceCodeLine.label() where parsedSourceCodeLine is
	 *               the name the user gave the ParseLine variable when it was
	 *               declared.
	 * @inparam none
	 * @outparam returns a string representing the label field for a parsed line
	 *           of source code
	 * @errorstested tested by parsing a complete test program (SourceCode.txt)
	 * @errorsgenerated Initially, there was errors with white space before and
	 *                  after comment fields. However, these bugs have been
	 *                  worked out (to the best of my knowledge).
	 * @author Nicholas Lister
	 * @createdate 10/14/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// returns the comment field of the line of source code
	public String comment() {
		return this.syntaxLine[3];
	}

	/**
	 * @modulename isEmptyLine
	 * @description returns a Boolean value representing whether or not the line
	 *              of source code is empty.
	 * @callsequence parsedSourceCodeLine.isEmptyLin() where
	 *               parsedSourceCodeLine is the name the user gave the
	 *               ParseLine variable when it was declared.
	 * @inparam none
	 * @outparam returns a Boolean representing whether or not the line of
	 *           source code is empty.
	 * @errorstested tested by parsing a complete test program (SourceCode.txt)
	 * @errorsgenerated no known errors
	 * @author Travis
	 * @createdate 10/16/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// returns true if the line is empty
	public Boolean isEmptyLine() {
		return this.isEmptyLine;
	}

	/**
	 * @modulename parseOperands
	 * @description This method parses and operand field (where the operands are
	 *              separated by commas) into an array of up to six operands.
	 *              Empty operand fields will have a null value.
	 * @callsequence parsedSourceCodeLine.parseOperands(String operands) where
	 *               parsedSourceCodeLine is the name the user gave the
	 *               ParseLine variable when it was declared.
	 * @inparam A String representing an operand field
	 * @outparam returns an array of up to six operands (as Strings). Empty
	 *           operand fields will have a null value.
	 * @errorstested tested by parsing various valid and invalid operand fields
	 * @errorsgenerated no known errors
	 * @author Nicholas Lister
	 * @createdate 10/16/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why Nick, 10/19/09 array size increased to six in order
	 *         to support use with parseExpression
     *         <li>Nick, 11/6/09, adjusted to handle leading minus signs
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	public static String[] parseOperands(String operandString) {
		String[] opArray = new String[6];
		int i = 0;

		// parse the first five...
		while ((operandString.length() > 0) && (i < 5)) {
			// look for a separator
			int commaIndex = operandString.indexOf(',');
			// if there is a separator
			if (commaIndex > -1) {
				// remove the label
				String operand = operandString.substring(0, commaIndex);
				operandString = operandString.substring(commaIndex);

				// store the operand
				opArray[i] = operand.trim();

				// remove the comma
				operandString = operandString.substring(1);
			} else // there is no comma so this is the last operand
			{
				// store the operand in the array
				opArray[i] = operandString.trim();
				// create a false while loop condition
				operandString = new String();
			}
			i++;
		}
		// the while loop removes the first five operands and if anything is
		// left
		// then that is the sixth operand
		if (operandString.length() > 0) {
			opArray[5] = operandString.trim();
		}
		return opArray;
	}

	/**
	 * @modulename parseExpression
	 * @description This method parses an expression operand field (where the
	 *              operands are separated by operators) into an array of up to
	 *              six operands. Empty operand fields will have a null value.
	 * @callsequence parsedSourceCodeLine.parseExpression(String operands) where
	 *               parsedSourceCodeLine is the name the user gave the
	 *               ParseLine variable when it was declared.
	 * @inparam A String representing an operand field for an expression
	 * @outparam returns an array of up to six operands (as Strings). Empty
	 *           operand fields will have a null value.
	 * @errorstested tested by parsing various valid and invalid operand fields
	 * @errorsgenerated no known errors
	 * @author Nicholas Lister
	 * @createdate 10/19/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why
	 *         <li>Nick, 11/6/09, adjusted to handle leading minus signs
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// parses an expression operand field into up to 6 operands
	// must be called with a valid expression!
	public static String[] parseExpression(String operand) {
		String[] opArray = new String[6];

		// check for * expression
		if (operand.charAt(0) == '*') {

			// first operand is *
			opArray[0] = "*";
			operand = operand.substring(1);

			// remove operator
			if (operand.length() > 0) {
				operand = operand.substring(1);
			}

			// parse remaining operands
			String[] tempOpArray = parseOperands(operand);

			// get remaining operands
			if (tempOpArray[0] != null) {
				opArray[1] = tempOpArray[0];
				if (tempOpArray[1] != null) {
					opArray[2] = tempOpArray[1].trim();
				}
			}
		} else {
			// check leading minus case
			String tempString = new String();
			if (operand.charAt(0) == '-') {
				tempString = operand.substring(0, 1);
				operand = operand.substring(1);
				while (operand.charAt(0) != '+' && operand.charAt(0) != '-'
						&& operand.charAt(0) != '*' && operand.charAt(0) != '/'
						&& operand.charAt(0) != '/') {
					tempString = tempString + operand.substring(0, 1);
					operand = operand.substring(1);
				}
			}
			operand = operand.replaceAll("\\+", "\\,");
			operand = operand.replaceAll("\\-", "\\,");
			operand = operand.replaceAll("\\*", "\\,");
			operand = operand.replaceAll("\\/", "\\,");
			operand = operand.replaceAll("\\=", "\\,");
			opArray = parseOperands(operand);
			if (tempString.length() > 0) {
				opArray[0] = tempString.trim();
			}
		}

		return opArray;
	}

	/**
	 * @modulename parseExpressionKeepOperators
	 * @description This method parses an expression operand field into an array
	 *              of up to six operands (with operators in front). Empty
	 *              operand fields will have a null value.
	 * @callsequence parsedSourceCodeLine.parseExpressionKeepOperators(String
	 *               operands) where parsedSourceCodeLine is the name the user
	 *               gave the ParseLine variable when it was declared.
	 * @inparam A String representing an operand field for an expression
	 * @outparam returns an array of up to six operands (as Strings). Empty
	 *           operand fields will have a null value.
	 * @errorstested tested by parsing various valid and invalid operand fields
	 *               and through use with OperandEvaluator
	 * @errorsgenerated no known errors
	 * @author Nicholas Lister
	 * @createdate 10/28/09
	 * @modlog <p>
	 *         <ul>
	 *         Who/When/Why Nick,10/30/09, finished code
	 *         <li>Nick, 11/6/09, adjusted to handle leading minus signs
	 *         </ul>
	 *         Coding Standards Met: "Signed Code Leader(Jaron)"
	 *         <p>
	 *         Testing Standards Met: "Signed Test Leader(Jamie)"
	 */
	// parses an expression operand field into up to 6 operands
	// must be called with a valid expression!
	public static String[] parseExpressionKeepOperators(String operand) {
		String[] opArray = new String[6];

		// check for * expression
		if (operand.charAt(0) == '*') {

			// first operand is *
			opArray[0] = "*";
			operand = operand.substring(1);

			// parse remaining operands
			String[] tempOpArray = parseOperands(operand);

			// get remaining operands

			if (tempOpArray[0] != null) {
				opArray[1] = tempOpArray[0].trim();
				if (tempOpArray[1] != null) {
					opArray[2] = tempOpArray[1].trim();
				}
			}

		} else if (operand.charAt(0) == '=') {
			opArray = parseOperands(operand);
		} else {
			int opNumber = 0;

			// handle expression '=' sign for expression 2
			if (operand.indexOf("=") > -1) {
				String tempString = operand.substring(0,
						operand.indexOf("=") + 1);
				operand = operand.substring(operand.indexOf("=") + 1);
				opArray[opNumber] = tempString.trim();
				opNumber++;
			}

			// get first operand (after equals sign if there was one)
			String tempString = new String();

			// leading minus check
			if (operand.charAt(0) == '-') {
				opArray[opNumber] = operand.substring(0, 1).trim();
				operand = operand.substring(1);
			}

			// separator check
			while (operand.charAt(0) != '+' && operand.charAt(0) != '-'
					&& operand.charAt(0) != '*' && operand.charAt(0) != '/'
					&& operand.charAt(0) != ',') {
				// if not separator then remove first character and put into
				// current operand
				tempString = tempString + operand.substring(0, 1);
				operand = operand.substring(1);

				if (operand.length() == 0) {
					break;
				}
			}
			if (opArray[opNumber] != null) {
				opArray[opNumber] = opArray[opNumber] + tempString.trim();
			} else {
				opArray[opNumber] = tempString.trim();
			}
			// move to next operand
			opNumber++;

			// get the operands in the middle of the first and last
			while (operand.length() > 0 && opNumber < 5) {
				// remove the operator and store in current operand
				tempString = operand.substring(0, 1);
				opArray[opNumber] = tempString.trim();
				operand = operand.substring(1);

				// separator check
				while (operand.charAt(0) != '+' && operand.charAt(0) != '-'
						&& operand.charAt(0) != '*' && operand.charAt(0) != '/'
						&& operand.charAt(0) != ',') {
					// if not separator then remove first character and put into
					// current operand
					tempString = operand.substring(0, 1);
					opArray[opNumber] = opArray[opNumber] + tempString.trim();
					operand = operand.substring(1);

					// stop parsing if there are no more operands
					if (operand.length() == 0) {
						break;
					}
				}
				// move to next operand
				opNumber++;
			}

			// get last operand
			if (operand.length() > 0) {
				opArray[5] = operand.trim();
			}
		}
		return opArray;
	}

	// test module
	public static void main(String[] args) {

		// get the test source code
		// File sourceText = new File("src//SourceCode.txt");
		// SourceCode.readFile(sourceText);

		// find the length of the test source code
		// int counter = SourceCode.returnOriginalCodeLength();
		//
		// // parse each line of source code
		// while (counter > 0) {
		// String testSourceLine = SourceCode.returnNextLine();
		// ParseLine testLine = new ParseLine(testSourceLine);
		//
		// // print results of ParseLine
		// testParseLine(testLine);
		// counter--;
		// }

		testParseOperands("label, label,");
		//testParseExpression("-10+1");
		//testParseExpressionKeepOperators("res=-10+1");
	}

	// prints out the results of a parsed line of code
	public static void testParseLine(ParseLine testLine) {
		// the period was added in the line of code below to be sure that the
		// comment field does not contain excess white space and so that we can
		// see that the comment field is not only white space (this should never
		// happen)

		System.out.println("Label:\t\t" + testLine.label() + ".");
		System.out.println("Instruction:\t" + testLine.instruction() + ".");
		System.out.println("Operand:\t" + testLine.operand() + ".");
		System.out.println("Comment:\t" + testLine.comment() + ".");
		System.out.println();
	}

	// tests parseOperands
	@SuppressWarnings("static-access")
	public static void testParseOperands(String operandString) {
		ParseLine parseLine = new ParseLine("");
		String[] operandArray = parseLine.parseOperands(operandString);
		System.out.println("parseOperands: ");
		System.out.println("Operand 1: " + operandArray[0]);
		System.out.println("Operand 2: " + operandArray[1]);
		System.out.println("Operand 3: " + operandArray[2]);
		System.out.println("Operand 4: " + operandArray[3]);
		System.out.println("Operand 5: " + operandArray[4]);
		System.out.println("Operand 6: " + operandArray[5]);
		System.out.println();
	}

	// tests parseExpression
	public static void testParseExpression(String operandString) {
		String[] operandArray = ParseLine.parseExpression(operandString);
		System.out.println("parseExpresion: ");
		System.out.println("Operand 1: " + operandArray[0]);
		System.out.println("Operand 2: " + operandArray[1]);
		System.out.println("Operand 3: " + operandArray[2]);
		System.out.println("Operand 4: " + operandArray[3]);
		System.out.println("Operand 5: " + operandArray[4]);
		System.out.println("Operand 6: " + operandArray[5]);
		System.out.println();
	}

	// tests parseExpressionKeepOperators
	@SuppressWarnings("static-access")
	public static void testParseExpressionKeepOperators(String operandString) {
		ParseLine parseLine = new ParseLine("");
		String[] operandArray = parseLine
				.parseExpressionKeepOperators(operandString);
		System.out.println("parseExpresionKeepOperators: ");
		System.out.println("Operand 1: " + operandArray[0]);
		System.out.println("Operand 2: " + operandArray[1]);
		System.out.println("Operand 3: " + operandArray[2]);
		System.out.println("Operand 4: " + operandArray[3]);
		System.out.println("Operand 5: " + operandArray[4]);
		System.out.println("Operand 6: " + operandArray[5]);
		System.out.println();
	}
}
